import socket
import subprocess
#连接目标
host = 'localhost'
port = 8080

#建立socket对象，基于对tcp的抽象，无需关注细节
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

#绑定，建立通信连接
s.bind((host,port))

#监听，确定连接的数量
s.listen(5)

#等待连接，并阻塞程序，知道收到客户端的连接请求
#accept返回一个元组-->元组中一个socket对象，一个客户端的IP、端口对象
print('start connecting')
#建立链接循环，用于同时链接多个客户端

#计数器，对链接的客户端进行计数
counter = 0
while True:
    conn_sock, conn_addr = s.accept()
    if conn_sock:
        counter += 1
        print('客户端个数：', counter)
    print(conn_addr)

    while True:   
        # 接受客户端发送的请求
        try:
            cmd = conn_sock.recv(1024)
            if not cmd:
                break
            print('客户端数据：', cmd.decode('utf-8'))
            # 接收到远程命令，使用 subprocess.Popen 启动一个子进程来执行命令
            # subprocess.Popen 函数作用：创建一个新的子进程并执行指定的命令
            # 参数说明：
            # - cmd.decode('utf-8')：将从客户端接收到的字节数据解码为 UTF-8 字符串，作为要执行的命令
            # - shell=True：通过系统的 shell 来执行命令，允许使用 shell 特有的功能，如管道、重定向等
            # - stdout=subprocess.PIPE：将子进程的标准输出重定向到一个管道，以便后续读取
            # - stderr=subprocess.PIPE：将子进程的标准错误输出重定向到一个管道，以便后续读取
            # 管道知识强调：管道是一种在进程间传递数据的机制，这里使用管道将子进程的输出和错误信息传递给父进程
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            # 使用 communicate 方法与子进程进行交互，等待子进程结束并获取其标准输出和标准错误输出
            # communicate 方法会返回一个元组，包含标准输出和标准错误输出的字节数据
            # 该方法能避免因管道缓冲区满导致的阻塞问题
            stdout, stderr = obj.communicate()
            # 发结果给客户端
            conn_sock.send(stdout + stderr)
        except ConnectionResetError:
            print('客户端断开连接')
            break

    # 关闭传输消息的socket
    conn_sock.close()
# 关闭计算机连接的socket
s.close()